import { reactive, watch } from "vue";
import { forEachValue, isPromise } from "./utils";

export function installModule(store, rootState, path, module) {
  const isRoot = !path.length; // 是否根模块
  const namespace = store._modules.getNamespace(path); // 获取命名空间(从根开始查找)

  if (!isRoot) {
    // const parentState = getNestedState(rootState, path.slice(0, -1));
    const parentState = path
      .slice(0, -1)
      .reduce((state, key) => state[key], rootState); // 获取父模块状态

    const moduleName = path[path.length - 1];
    // 更改状态需要在 _withCommit 方法中执行才不会报错
    store._withCommit(() => {
      parentState[moduleName] = module.state;
    });
  }

  const local = (module.context = makeLocalContext(store, namespace, path)); // 当前模块上下文数据

  module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key;
    const entry =
      store._mutations[namespacedType] ||
      (store._mutations[namespacedType] = []); // 订阅每个模块中方法
    entry.push((payload) => {
      mutation.call(store, getNestedState(store.state, path), payload);
    });
  });

  module.forEachAction((action, key) => {
    const type = action.root ? key : namespace + key;
    const handler = action.handler || action;
    const entry = store._actions[type] || (store._actions[type] = []);
    entry.push((payload) => {
      let res = handler.call(
        store,
        {
          dispatch: local.dispatch,
          commit: local.commit,
          state: getNestedState(store.state, path),
          rootGetters: store.getters,
          rootState: store.state,
        },
        payload
      ); // action 执行后会返回一个 promise
      if (!isPromise(res)) {
        return Promise.resolve(res); // 不是 promise 包装成 promsie
      }
      return res;
    });
  });

  module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key; // 属性名为空间名加模块上自身属性名
    store._wrappedGetters[namespacedType] = () => {
      return getter(getNestedState(store.state, path)); // 将数据响应式
    };
  });

  module.forEachChild((child, key) => {
    // 递归依次安装子模块状态
    installModule(store, rootState, path.concat(key), child);
  });
}

// 重置仓库
export function resetStore(store) {
  store._actions = Object.create(null);
  store._mutations = Object.create(null);
  store._wrappedGetters = Object.create(null);
  const state = store.state;
  // 安装模块
  installModule(store, state, [], store._modules.root, true);
  // 重置状态
  resetStoreState(store, state);
}

// 重置仓库中状态
export function resetStoreState(store, state) {
  store.getters = {};
  const wrappedGetters = store._wrappedGetters;
  // 遍历计算属性
  forEachValue(wrappedGetters, (getter, key) => {
    Object.defineProperty(store.getters, key, {
      get: () => getter(),
      enumerable: true,
    });
  });
  store._state = reactive({ data: state }); // 将状态响应式挂载在 store 上的 _state 属性的 data

  // 是否严格模式
  if (store.strict) {
    enableStrictMode(store);
  }
}

// 监听状态变化
export function enableStrictMode(store) {
  // 监听仓库状态变化
  watch(
    () => store._state.data,
    () => {
      // 提示错误
      console.assert(
        store._committing, // 只有当 true 才不会打印错误，而 _committing 为真只有在 _withCommit 方法中才是
        `do not mutate vuex store state outside mutation handlers.`
      );
    },
    { deep: true, flush: "sync" } // 深度监听同步监听
  );
}

// 发布订阅
export function genericSubscribe(fn, subs, options) {
  if (subs.indexOf(fn) < 0) {
    options && options.prepend ? subs.unshift(fn) : subs.push(fn); // 前添加还是后添加
  }
  // 取消监听函数
  return () => {
    const i = subs.indexOf(fn);
    if (i > -1) {
      subs.splice(i, 1);
    }
  };
}

export function getNestedState(state, path) {
  return path.reduce((state, key) => state[key], state);
}

// 创建当前上下文包含 dispatch, commit 方法
export function makeLocalContext(store, namespace, path) {
  const noNamespace = namespace === "";
  const local = {
    dispatch: noNamespace
      ? store.dispatch
      : (type, payload, options) => {
          if (!options || !options.root) {
            type = namespace + type; // 拼前缀空间命名
          }
          return store.dispatch(type, payload);
        },
    commit: noNamespace
      ? store.commit
      : (type, payload, options) => {
          if (!options || !options.root) {
            type = namespace + type; // 有空间命名则派发时补齐前缀
          }
          store.commit(type, payload, options);
        },
  };
  Object.defineProperties(local, {
    state: {
      get: () => getNestedState(store.state, path),
    },
  });
  return local;
}
